/**
 * @description koa-bundler
 *
 * @author xiaomi
 */

const path = require('path');
const fsp = require('fs-extra');

const webpack = require('webpack');
const webpackRunner = require('./webpack-runner');
const webpackDevMW = require('./koa-webpack-dev');

module.exports = function({
    dllConfigName = null,
    appConfigName = 'webpack.config',
    dllFileName = 'vendor.js',
    publicPath = '/',
    viewsPath = '.'
}) {
    const buildDLL = function() {
        if(buildDLL.promise) {
            return buildDLL.promise;
        }

        const dllCfgPath = path.join(viewsPath, dllConfigName);

        buildDLL.promise = fsp.exists(dllCfgPath)
        .then(exists => {
            if(exists) {
                throw new Error('dll config not exisits');
            }

            return require(dllCfgPath);
        })
        .then(dllCfg => {
            // Force disable bail
            dllCfg = Object.assign({}, dllCfg, {
                bail: false
            });

            return webpackRunner(dllCfg)
            .then(() => {
                return dllCfg;
            });
        })
        .then(dllCfg => {
            const dllOutput = dllCfg.output;
            const dllJSPath = path.join(dllOutput.path, dllFileName);
            const dllJSURI = path.join(publicPath, dllFileName);
            const dllMapPath = dllJSPath + '.map';
            const dllMapURI = dllJSURI + '.map';

            return {
                dllMapPath: dllMapPath,
                dllJSPath: dllJSPath,
                dllMapURI: dllMapURI,
                dllJSURI: dllJSURI,

                get(type) {
                    let p = dllJSPath;
                    if(type === 'map') {
                        p = dllMapPath;
                    }

                    return fsp.createReadStream(p);
                }
            };
        });

        return buildDLL.promise;
    };

    const buildApp = function(uri) {
        const appCfgPath = path.join(viewsPath, appConfigName);

        // Lazy init to wait dll ready
        if(!buildApp.compiler) {
            // Force disable bail
            const appCfg = Object.assign({}, require(appCfgPath), {
                bail: false
            });

            buildApp.compiler = webpack(appCfg);
        }

        if(!buildApp.mw) {
            buildApp.mw = webpackDevMW(buildApp.compiler, {
                publicPath: publicPath,
                headers: {
                    'X-Builder': 'webpack'
                },
                stats: {
                    version: false,
                    assets: false,
                    chunks: false,
                    child: false,
                    hash: false
                },
                noInfo: true,
                lazy: true
            });

            buildApp.fs = buildApp.mw.fileSystem;
            buildApp.getFilenameFromUrl = buildApp.mw.getFilenameFromUrl;

            buildApp.getMapMiddleware = (uri) => {
                return function *() {
                    const filePath = buildApp.getFilenameFromUrl(uri);

                    this.body = buildApp.fs.createReadStream(filePath);
                };
            };
        }

        // map file dependent on js file, read direct
        if(uri.slice(-4) === '.map') {
            return buildApp.getMapMiddleware(uri);
        }

        return buildApp.mw;
    };

    return function *(next) {
        yield next;

        if(
            this.body ||
            this.status !== 404 ||
            this.path.indexOf(publicPath) !== 0
        ) {
            return;
        }

        if(dllConfigName) {
            const uri = this.path;
            const dll = yield buildDLL();

            if(uri === dll.dllJSURI) {
                this.type = 'application/javascript';
                this.body = dll.get('js');

                return;
            }
            else if(uri === dll.dllMapURI) {
                this.type = 'application/json';
                this.body = dll.get('map');

                return;
            }
        }

        yield buildApp(this.path);
    };
};
